package com.web_bloks.jsonp.client;

import java.util.Iterator;
import java.util.Vector;

import com.web_bloks.system.client.Logger;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.Timer;

/**
 * This class orchestrates the JSONP fetch. 
 * Handles timeouts e.g. scripts that fail to include
 * a wrapper function (due to error etc.)
 * 
 * USAGE:
 * - configure (listeners, timeout)
 * - set params
 * - set url
 * - fetch() 
 * 
 * @author Jean-Lou Dupont
 *
 */
abstract public class JSONPFetcher 
	implements BaseCallbackEvent {

	final static int _default_timeout = 3000;
	final static int _default_operationTimeout = 5000;

	Timer timer = null;
	boolean isBusy = false;
	
	/**
	 * Call object
	 */
	JSONPcall     jsonc;
	/**
	 * Callback object
	 */
	JSONPcallback jsoncb;
	
	/**
	 * Timeout for HTTP call
	 */
	int timeout = 3000;
	/**
	 * Operation level timeout
	 */
	int operationTimeout = 5000;
	
	/**
	 * Listeners
	 */
	Vector<JSONPCallListener> listeners = null;
	
	/**
	 * Parameter name for the callback field.
	 *  This is service-specific i.e. Delicious has one etc.
	 */
	String callbackParameterName = null;
	
	/**
	 * 
	 */
	JSONObject currentJSONObj = null;

	public JSONPFetcher() {
		super();
		setup();
	}
	private void setup() {
		this.jsonc = new JSONPcall();
		this.jsoncb= new JSONPcallback();
		this.listeners = new Vector<JSONPCallListener>();
		
		this.jsoncb.setTarget(this);
		this.currentJSONObj = null;
	}

	/*===================================================================
	 * CONFIGURATION
	 ===================================================================*/
	/**
	 * Operation level timeout
	 */
	public void setOperationTimeout( int timeout ) {
		this.operationTimeout = timeout;
	}
	/**
	 * Used in the HTTP call made
	 *  from JSONcall
	 */
	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}
	/**
	 * addCallListener
	 * @param s CallListener source
	 */
	public void addCallListener(JSONPCallListener s) {
		Logger.logInfo(getClass()+".addCallListener: called.");
		this.listeners.add( s );
	}
	/**
	 * removeCallListener
	 * @param s CallListener source
	 */
	public void removeCallListener(JSONPCallListener s) {
		Logger.logInfo(getClass()+".removeCallListener: called.");		
		this.listeners.remove( s );
	}
	/*===================================================================
	 * API
	 ===================================================================*/
	public void setUrl(String url) {
		this.jsonc.setUrl(url);
	}
	public void addParam(String key, String value) {
		this.jsonc.addParam(key, value);
	}
	/**
	 * fetch
	 *  This is the main method.
	 * @return boolean
	 */
	public boolean fetch() {
	
		//prepare the call
		this.jsonc.setTimeout(this.timeout);
		
		//set callback
		this.jsoncb.create();

		//set callback parameter name
		this.jsonc.addParam(this.callbackParameterName, this.getCallbackFunctionName());
		
		//start operation
		this.startOperation(this.operationTimeout);
		
		//do the call
		this.jsonc.call();
		
		return true;
	}
	/**
	 */
	public String getCallbackFunctionName() {
		return this.jsoncb.getCallbackName();
	}
	/**
	 * 
	 * @param cbName
	 */
	public void setCallbackParameterName(String cbName) {
		this.callbackParameterName = cbName;
	}
	/*===================================================================
	 * BaseCallbackEvent 
	 ===================================================================*/
	/**
	 * transformJSONObject
	 * Must be defined in derived class
	 * 
	 * @pattern Template Method
	 */
	abstract protected Object transformJSONObject( JSONObject o );
	
	/**
	 * This handler is called when the callback is triggered
	 * @see com.web_bloks.jsonp.client.JSONcallback
	 */
	public void handleCallbackEvent(int id, JavaScriptObject obj) {
		
		this.currentJSONObj = new JSONObject( obj );
		JSONPCallbackResponse cro = new JSONPCallbackResponse( this.transformJSONObject( this.currentJSONObj ) );
		
		this.timerCancel();
		
		Logger.logInfo(getClass()+".handleCallbackEvent: called.");
		
		this.notifyListeners( cro );
	}
	
	public JSONObject getJSONObject() {
		return this.currentJSONObj;
	}
	
	/*===================================================================
	 * CallListener 
	 ===================================================================*/
	protected void notifyListeners(JSONPCallbackResponse obj) {
		
		Iterator<JSONPCallListener> it = this.listeners.iterator();
		while (it.hasNext()) {
			Object o = it.next ();
		    ((JSONPCallListener) o).fireJSONPCallEvent( obj );
		}
		
	}
	
	/*===================================================================
	 * Operation related 
	 ===================================================================*/
	/**
	 * This method is called when the operation times-out.
	 * Resets 'busy' status.
	 */
	public void timerExpiredEvent() {
		this.isBusy = false;
		
		Logger.log(getClass()+".timerExpiredEvent: called.");
		
		// create a 'timeout' event object
		JSONPCallbackResponse cro = new JSONPCallbackResponse( JSONPCallbackResponse.Code.TIMEOUT );
		this.notifyListeners( cro );
	}
	
	/*===================================================================
	 * ObjectPool related 
	 ===================================================================*/
	/**
	 */
	public void _clean() {
		this.jsonc._clean();
		this.jsoncb._clean();
		this.listeners.clear();
		this.timeout = _default_timeout;
		this.operationTimeout = _default_operationTimeout;
		this.currentJSONObj = null;
	}

	//
	/**
	 * Used in derived classes.
	 * @param Timeout int (in ms)
	 */
	protected void startOperation(int timeout) {
		
		// prepare a timer
		if ( this.timer == null ) {
			this.timer = new Timer() {
				public void run() {
					timerExpiredEvent();
				}
			};
			
			this.isBusy = false;
		}

		// busy already?
		if ( this.isBusy ) {
			throw new RuntimeException(getClass()+".startOperation : already busy");
		}
		
		this.isBusy = true;
		
		this.timer.schedule( timeout );
	}
	/**
	 * Returns 'busy' status.
	 * @return boolean
	 */
	public boolean getBusy() {
		return this.isBusy;
	}
	/**
	 * Cancels the current operation
	 */
	public void timerCancel() {
		
		if ( this.timer != null ) {
			this.timer.cancel();
			this.isBusy = false;
			Logger.logWarn(getClass()+".timerCancel: method called");			
		} else {
			Logger.logWarn(getClass()+".timerCancel: NO TIMER active!");	
		}
		
	}
	
	
}//END